package me.buick.util.jmeter.snmpvisualizers;

import static javax.swing.SwingConstants.EAST;
import static javax.swing.SwingConstants.SOUTH;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.TitledBorder;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.basic.BasicGraphicsUtils;


public class SNMPBorderedComponent extends JPanel implements ActionListener {
    private static final boolean IS_WIN = false;
	JButton moreOrLessButton;
    String valueLabelStr;
    JLabel label;
    JComponent comp;
    boolean collapsed = false;

    private JPopupMenu popupMenu;

    private Icon collapseIcon;
    private Icon expandIcon;

    private static Image getImage(String name) {
	Toolkit tk = Toolkit.getDefaultToolkit();
	name = "resources/" + name + ".png";
	return tk.getImage(SNMPBorderedComponent.class.getResource(name));
    }

    public SNMPBorderedComponent(String text) {
	this(text, null, false);
    }
    
    public SNMPBorderedComponent(String text, JComponent comp) {
	this(text, comp, false);
    }
    
    public SNMPBorderedComponent(String text, JComponent comp, boolean collapsible) {
	super(null);

	this.comp = comp;

	// Only add border if text is not null
	if (text != null) {
	    TitledBorder border;
	    if (collapsible) {
		final JLabel textLabel = new JLabel(text);
		JPanel borderLabel = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 0));
		borderLabel.add(textLabel);
		border = new LabeledBorder(borderLabel);
		textLabel.setForeground(border.getTitleColor());

		if (IS_WIN) {
		    collapseIcon = new ImageIcon(getImage("collapse-winlf"));
		    expandIcon = new ImageIcon(getImage("expand-winlf"));
		} else {
		    collapseIcon = new ArrowIcon(SOUTH, textLabel);
		    expandIcon = new ArrowIcon(EAST, textLabel);
		}

		moreOrLessButton = new JButton(collapseIcon);
		moreOrLessButton.setContentAreaFilled(false);
		moreOrLessButton.setBorderPainted(false);
		moreOrLessButton.setMargin(new Insets(0, 0, 0, 0));
		moreOrLessButton.addActionListener(this);
		String toolTip = "BorderedComponent.moreOrLessButton.toolTip";
		moreOrLessButton.setToolTipText(toolTip);
		borderLabel.add(moreOrLessButton);
		borderLabel.setSize(borderLabel.getPreferredSize());
		add(borderLabel);
	    } else {
		border = new TitledBorder(text);
	    }
	    setBorder(new CompoundBorder(new FocusBorder(this), border));
	} else {
	    setBorder(new FocusBorder(this));
	}
	if (comp != null) {
	    add(comp);
	}
    }

    public void setComponent(JComponent comp) {
	if (this.comp != null) {
	    remove(this.comp);
	}
	this.comp = comp;
	if (!collapsed) {
	    LayoutManager lm = getLayout();
	    if (lm instanceof BorderLayout) {
		add(comp, BorderLayout.CENTER);
	    } else {
		add(comp);
	    }
	}
	revalidate();
    }

    public void setValueLabel(String str) {
	this.valueLabelStr = str;
	if (label != null) {
	    label.setText("Current value");
	}
    }

    public void actionPerformed(ActionEvent ev) {
	if (collapsed) {
	    if (label != null) {
		remove(label);
	    }
	    add(comp);
	    moreOrLessButton.setIcon(collapseIcon);
	} else {
	    remove(comp);
	    if (valueLabelStr != null) {
		if (label == null) {
		    label = new JLabel("Current value");
		}
		add(label);
	    }
	    moreOrLessButton.setIcon(expandIcon);
	}
	collapsed = !collapsed;

	JComponent container = (JComponent)getParent();
	if (container != null &&
	    container.getLayout() instanceof SNMPVariableGridLayout) {

	    ((SNMPVariableGridLayout)container.getLayout()).setFillRow(this, !collapsed);
	    container.revalidate();
	}
    }

    public Dimension getMinimumSize() {
	if (getLayout() != null) {
	    // A layout manager has been set, so delegate to it
	    return super.getMinimumSize();
	}

	if (moreOrLessButton != null) {
	    Dimension d = moreOrLessButton.getMinimumSize();
	    Insets i = getInsets();
	    d.width  += i.left + i.right;
	    d.height += i.top + i.bottom;
	    return d;
	} else {
	    return super.getMinimumSize();
	}
    }

    public void layout() {
	if (getLayout() != null) {
	    // A layout manager has been set, so delegate to it
	    super.layout();
	    return;
	}

	Dimension d = getSize();
	Insets i = getInsets();

	if (collapsed) {
	    if (label != null) {
		Dimension p = label.getPreferredSize();
		label.setBounds(i.left,
				i.top + (d.height - i.top - i.bottom - p.height) / 2,
				p.width,
				p.height);
	    }
	} else {
	    if (comp != null) {
		comp.setBounds(i.left,
			       i.top,
			       d.width - i.left - i.right,
			       d.height - i.top - i.bottom);
	    }
	}
    }

    private static class ArrowIcon implements Icon {
	private int direction;
	private JLabel textLabel;

	public ArrowIcon(int direction, JLabel textLabel) {
	    this.direction = direction;
	    this.textLabel = textLabel;
	}

	public void paintIcon(Component c, Graphics g, int x, int y) {
	    int w = getIconWidth();
	    int h = w;
	    Polygon p = new Polygon();
	    switch (direction) {
	      case EAST:
		p.addPoint(x + 2,     y);
		p.addPoint(x + w - 2, y + h / 2);
		p.addPoint(x + 2,     y + h - 1);
		break;

	      case SOUTH:
		p.addPoint(x,         y + 2);
		p.addPoint(x + w / 2, y + h - 2);
		p.addPoint(x + w - 1, y + 2);
		break;
	    }
	    g.fillPolygon(p);
	}

	public int getIconWidth() {
	    return getIconHeight();
	}

	public int getIconHeight() {
	    Graphics g = textLabel.getGraphics();
	    if (g != null) {
		int h = g.getFontMetrics(textLabel.getFont()).getAscent() * 6/10;
		if (h % 2 == 0) {
		    h += 1;	// Make it odd
		}
		return h;
	    } else {
		return 7;
	    }
	}
    }


    /**
     * A subclass of <code>TitledBorder</code> which implements an arbitrary border
     * with the addition of a JComponent (JLabel, JPanel, etc) in the
     * default position.
     * <p>
     * If the border property value is not 
     * specified in the constuctor or by invoking the appropriate
     * set method, the property value will be defined by the current
     * look and feel, using the following property name in the
     * Defaults Table:
     * <ul>
     * <li>&quot;TitledBorder.border&quot;
     * </ul>
     */
    protected static class LabeledBorder extends TitledBorder {
	protected JComponent label;

	private Point compLoc = new Point();

	/**
	 * Creates a LabeledBorder instance.
	 * 
	 * @param label  the label the border should display
	 */
	public LabeledBorder(JComponent label)     {
	    this(null, label);
	}

	/**
	 * Creates a LabeledBorder instance with the specified border
	 * and an empty label.
	 * 
	 * @param border  the border
	 */
	public LabeledBorder(Border border)       {
	    this(border, null);
	}

	/**
	 * Creates a LabeledBorder instance with the specified border and
	 * label.
	 * 
	 * @param border  the border
	 * @param label  the label the border should display
	 */
	public LabeledBorder(Border border, JComponent label) {
	    super(border);

	    this.label = label;

	    if (label instanceof JLabel &&
		label.getForeground() instanceof ColorUIResource) {

		label.setForeground(getTitleColor());
	    }

	}

	/**
	 * Paints the border for the specified component with the 
	 * specified position and size.
	 * @param c the component for which this border is being painted
	 * @param g the paint graphics
	 * @param x the x position of the painted border
	 * @param y the y position of the painted border
	 * @param width the width of the painted border
	 * @param height the height of the painted border
	 */
	public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {

//	    Border border = getBorder();
//
//	    if (label == null) {
//		if (border != null) {
//		    border.paintBorder(c, g, x, y, width, height);
//		}
//		return;
//	    }
//
//	    Rectangle grooveRect = new Rectangle(x + EDGE_SPACING, y + EDGE_SPACING,
//						 width - (EDGE_SPACING * 2),
//						 height - (EDGE_SPACING * 2));
//
//	    Dimension   labelDim = label.getPreferredSize();
////	    int baseline = label.getBaseline(labelDim.width, labelDim.height);
////	    int         ascent = Math.max(0, baseline);
////	    int         descent = labelDim.height - ascent;
////	    int         diff;
//	    Insets      insets;
//
//	    if (border != null) {
//		insets = border.getBorderInsets(c);
//	    } else {
//		insets = new Insets(0, 0, 0, 0);
//	    }
//
////	    diff = Math.max(0, ascent/2 + TEXT_SPACING - EDGE_SPACING);
////	    grooveRect.y += diff;
////	    grooveRect.height -= diff;
////	    compLoc.y = grooveRect.y + insets.top/2 - (ascent + descent) / 2 - 1;
//
//	    int justification;
//	    if (c.getComponentOrientation().isLeftToRight()) {
//		justification = LEFT;
//	    } else {
//		justification = RIGHT;
//	    }
//
//	    switch (justification) {
//		case LEFT:
//		    compLoc.x = grooveRect.x + TEXT_INSET_H + insets.left;
//		    break;
//		case RIGHT:
//		    compLoc.x = (grooveRect.x + grooveRect.width
//				 - (labelDim.width + TEXT_INSET_H + insets.right));
//		    break;
//	    }
//
//	    // If title is positioned in middle of border AND its fontsize
//	    // is greater than the border's thickness, we'll need to paint 
//	    // the border in sections to leave space for the component's background 
//	    // to show through the title.
//	    //
//	    if (border != null) {
//		if (grooveRect.y > compLoc.y - ascent) {
//		    Rectangle clipRect = new Rectangle();
//
//		    // save original clip
//		    Rectangle saveClip = g.getClipBounds();
//
//		    // paint strip left of text
//		    clipRect.setBounds(saveClip);
//		    if (computeIntersection(clipRect, x, y, compLoc.x-1-x, height)) {
//			g.setClip(clipRect);
//			border.paintBorder(c, g, grooveRect.x, grooveRect.y,
//				      grooveRect.width, grooveRect.height);
//		    }
//
//		    // paint strip right of text
//		    clipRect.setBounds(saveClip);
//		    if (computeIntersection(clipRect, compLoc.x+ labelDim.width +1, y,
//				   x+width-(compLoc.x+ labelDim.width +1), height)) {
//			g.setClip(clipRect);
//			border.paintBorder(c, g, grooveRect.x, grooveRect.y,
//				      grooveRect.width, grooveRect.height);
//		    }
//
//		    // paint strip below text
//		    clipRect.setBounds(saveClip);
//		    if (computeIntersection(clipRect,
//					    compLoc.x - 1, compLoc.y + ascent + descent, 
//					    labelDim.width + 2,
//					    y + height - compLoc.y - ascent - descent)) {
//			g.setClip(clipRect);
//			border.paintBorder(c, g, grooveRect.x, grooveRect.y,
//				  grooveRect.width, grooveRect.height);
//		    }
//
//		    // restore clip
//		    g.setClip(saveClip);   
//
//		} else {
//		    border.paintBorder(c, g, grooveRect.x, grooveRect.y,
//				      grooveRect.width, grooveRect.height);
//		}
//
//		label.setLocation(compLoc);
//		label.setSize(labelDim);
//	    }
	}

	/** 
	 * Reinitialize the insets parameter with this Border's current Insets. 
	 * @param c the component for which this border insets value applies
	 * @param insets the object to be reinitialized
	 */
	public Insets getBorderInsets(Component c, Insets insets) {
	    int height = 16;

	    Border border = getBorder();
	    if (border != null) {
		if (border instanceof AbstractBorder) {
		    ((AbstractBorder)border).getBorderInsets(c, insets);
		} else {
		    // Can't reuse border insets because the Border interface
		    // can't be enhanced.
		    Insets i = border.getBorderInsets(c);
		    insets.top = i.top;
		    insets.right = i.right;
		    insets.bottom = i.bottom;
		    insets.left = i.left;
		}
	    } else {
		insets.left = insets.top = insets.right = insets.bottom = 0;
	    }

	    insets.left += EDGE_SPACING + TEXT_SPACING;
	    insets.right += EDGE_SPACING + TEXT_SPACING;
	    insets.top += EDGE_SPACING + TEXT_SPACING;
	    insets.bottom += EDGE_SPACING + TEXT_SPACING;

	    if (c == null || label == null) {
		return insets;
	    }

	    insets.top += label.getHeight();

	    return insets;
	}

	/**
	 * Returns the label of the labeled border.
	 */
	public JComponent getLabel() {
	    return label;
	}


	/**
	 * Sets the title of the titled border.
	 * param title the title for the border
	 */
	public void setLabel(JComponent label) {
	    this.label = label;
	}



	/**
	 * Returns the minimum dimensions this border requires
	 * in order to fully display the border and title.
	 * @param c the component where this border will be drawn
	 */
	public Dimension getMinimumSize(Component c) {
	    Insets insets = getBorderInsets(c);
	    Dimension minSize = new Dimension(insets.right + insets.left, 
					      insets.top + insets.bottom);
	    minSize.width += label.getWidth();

	    return minSize;       
	}


	private static boolean computeIntersection(Rectangle dest, 
						   int rx, int ry, int rw, int rh) {
	    int x1 = Math.max(rx, dest.x);
	    int x2 = Math.min(rx + rw, dest.x + dest.width);
	    int y1 = Math.max(ry, dest.y);
	    int y2 = Math.min(ry + rh, dest.y + dest.height);
	    dest.x = x1;
	    dest.y = y1;
	    dest.width = x2 - x1;
	    dest.height = y2 - y1;

	    if (dest.width <= 0 || dest.height <= 0) {
		return false;
	    }
	    return true;
	}  
    }


    protected static class FocusBorder extends AbstractBorder implements FocusListener {
	private Component comp;
	private Color focusColor;
	private boolean focusLostTemporarily = false;

	public FocusBorder(Component comp) {
	    this.comp = comp;

	    comp.addFocusListener(this);

	    // This is the best guess for a L&F specific color
	    focusColor = UIManager.getColor("TabbedPane.focus");
	}

	public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
	    if (comp.hasFocus() || focusLostTemporarily) {
		Color color = g.getColor();
		g.setColor(focusColor);
		BasicGraphicsUtils.drawDashedRect(g, x, y, width, height);
		g.setColor(color);
	    }
	}

	public Insets getBorderInsets(Component c)       { 
	    return getBorderInsets(c, new Insets(0, 0, 0, 0));
	}

	public Insets getBorderInsets(Component c, Insets insets) {
	    insets.left = insets.top = insets.right = insets.bottom = 2;
	    return insets;
	}


	public void focusGained(FocusEvent e) {
	    comp.repaint();
	}

	public void focusLost(FocusEvent e) {
	    // We will still paint focus even if lost temporarily
	    focusLostTemporarily = e.isTemporary();
	    if (!focusLostTemporarily) {
		comp.repaint();
	    }
	}
    }
}
